/*
 * Copyright 2007. Mount Sinai Hospital, Toronto, Canada.
 * 
 * Licensed under the GNU Lesser General Public License, Version 2. You
 * can find a copy of the license at:
 * 
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 * 
 * IN NO EVENT SHALL MOUNT SINAI HOSPITAL BE LIABLE TO ANY PARTY FOR DIRECT, 
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST 
 * PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, 
 * EVEN IF MOUNT SINAI HOSPITAL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 
 * DAMAGE.
 * 
 * MOUNT SINAI HOSPITAL SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF 
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND 
 * ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". 
 * MOUNT SINAI HOSPITAL HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, 
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 
 */
package com.sinai.mshab.client.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import com.google.gwt.user.client.rpc.IsSerializable;
import com.sinai.mshab.client.MSHABConfig;
import com.sinai.mshab.client.ui.Key;

/**
 * Object that stores answers from <code>QuestionComposite</code>s.
 * <code>QuestionComposite</code>s have field(s) which are filled in by the
 * user. These fields are our <i>answer</i> and we store these in the
 * <code>Answer</code> object.
 * 
 * @author Arthur Kalmenson
 * @see IsSerializable
 */
public class Answer implements IsSerializable {

	/**
	 * This field contains a map between a question and an answer. <b>Note:</b>
	 * The <code>HashMap</code> must contain Strings as its keys and values.
	 * 
	 * @gwt.typeArgs <java.lang.String, java.lang.String>
	 */
	private HashMap answers = new HashMap();

	/**
	 * The name of the database table where this <code>Answer</code> object
	 * belongs.
	 */
	private String tableName;

	/**
	 * Specifies whether or not this answer can be merged with other answers.
	 * 
	 */
	private boolean mergeable = true;

	/**
	 * Contains the where clauses for sql statements.
	 * 
	 * @gwt.typeArgs <com.sinai.mshab.client.util.WhereClause>
	 */
	private ArrayList where_clauses = new ArrayList();

	/**
	 * Contains a list of keys that mark the rows that should be removed.
	 * 
	 * @gwt.typeArgs <com.sinai.mshab.client.ui.Key>
	 */
	private ArrayList remove_keys = new ArrayList();

	/**
	 * Adds a new answer using <code>fieldName</code> as the key and
	 * <code>value</code> as the value. Upon success, true is returned. If
	 * there was some error (i.e. empty field), false is returned.
	 * 
	 * @param tableName
	 *            the name of the table where the field is stored.
	 * @param fieldName
	 *            the field name in the database where <code>value</code> is
	 *            to be stored.
	 * @param value
	 *            the user inputed data.
	 */
	public void add(String tableName, String fieldName, String value) {

		// if fieldName isn't null or empty and value isn't null, we add it to
		// the HashMap.
		if (!Util.nullEmpty(fieldName) && value != null) {
			answers.put(fieldName, value);
		}

		// if the tableName is not null or empty, we add it.
		if (!Util.nullEmpty(tableName)) {
			this.tableName = tableName;
		}
	}

	/**
	 * Merges <i>this</i> <code>Answer</code> with <code>answer</code>,
	 * effectively adding <code>answer</code>. If there are conflicting key,
	 * value tuples, the tuple in <code>answer</code> will overwrite the
	 * existing value. Upon success, true is returned. If there was some error
	 * (i.e. empty field), false is returned.
	 * 
	 * @param answer
	 *            the new answer to add.
	 * @return true if added successfully, false otherwise.
	 */
	public void add(Answer answer) {

		// put everything in answer's answers into our answers.
		if (answer.getHashMap() != null) {
			answers.putAll(answer.getHashMap());
		}

		// if there are any keys to remove, we add them.
		if (answer.getDeleteKeys() != null || answer.getDeleteKeys().size() > 0) {
			remove_keys.addAll(answer.getDeleteKeys());
		}

		// if there are any where clauses, we add them as well.
		if (answer.getWhereClauses() != null
				|| answer.getWhereClauses().size() > 0) {
			where_clauses.addAll(answer.getWhereClauses());
		}
	}

	/**
	 * Returns the value (answer) that is associated with <code>fieldName</code>
	 * (question). If no such value/field exists, <code>null</code> is
	 * returned.
	 * 
	 * @param fieldName
	 *            the field name (1-to-1 relationship with the DB) associated
	 *            with a value
	 * @return the value based on <code>fieldName</code> or <code>null</code>
	 *         if no such value/field.
	 */
	public String get(String fieldName) {

		String result = null;

		// grab a key interator and iterate over the keys
		Iterator keys = answers.keySet().iterator();
		while (keys.hasNext()) {
			Object next_key = keys.next();

			// if the key matches the fieldName, we set the value to result.
			if (fieldName.equals((String) next_key)) {
				result = (String) answers.get(next_key);
			}
		}

		return result;
	}

	/**
	 * Adds a {@link WhereClause} to this Answer object. We assume that the
	 * operator between the field and the value is equal. Note, field and value
	 * must be instantiated and field cannot be empty, otherwise an
	 * {@link IllegalArgumentException} will be thrown.
	 * 
	 * @param field
	 *            the field, or key, in the WhereClause.
	 * @param value
	 *            the value the field will equal.
	 */
	public void addWhereClause(String field, String value) {

		if (Util.nullEmpty(field) || value == null) {
			throw new IllegalArgumentException(
					MSHABConfig.MSHAB_ERROR_NULL_ARGS);
		}
		WhereClause w = new WhereClause();
		w.setColumn(field);
		w.setValue(value);
		where_clauses.add(w);
	}

	/**
	 * Return the where clauses associated with this answer collection.
	 * 
	 * @return the where clauses associated with this answer collection.
	 */
	public ArrayList getWhereClauses() {
		return where_clauses;
	}

	/**
	 * Adds a {@link Key} that identifies a row in the table to be removed. Note
	 * that key must be instantiated and non-empty otherwise an
	 * {@link IllegalArgumentException} will be thrown.
	 * 
	 * @param key
	 *            the key identifies a row in the table to be removed.
	 */
	public void addDeleteKey(Key key) {
		if (key == null || key.isEmpty()) {
			throw new IllegalArgumentException(
					MSHABConfig.MSHAB_ERROR_NULL_ARGS);
		}
		remove_keys.add(key);
	}

	/**
	 * Returns the keys that are meant to be deleted.
	 * 
	 * @return the keys that are meant to be deleted.
	 */
	public ArrayList getDeleteKeys() {
		return remove_keys;
	}

	/**
	 * If an <code>Answer</code> object has only one answer, that answer will
	 * be returned. Otherwise, this function will return <code>null</code>.
	 * 
	 * @return the answer if there is only one, <code>null</code> otherwise.
	 */
	public String getFirstAnswer() {

		String result = null;

		// grab an entry Set iterator
		Iterator key_val_iter = answers.entrySet().iterator();
		if (key_val_iter.hasNext()) {

			Map.Entry tuple = (Map.Entry) key_val_iter.next();

			/*
			 * if the tuple has only one answer (since that's the behaviour of
			 * this function), then we set the result to be the value of the
			 * only entry.
			 */
			if (!key_val_iter.hasNext()) {
				result = (String) tuple.getValue();
			}
		}

		return result;
	}

	/**
	 * Returns the <code>HashMap</code> of answers. <b>Note:</b> The
	 * <code>HashMap</code> contain Strings as its keys and values.
	 * 
	 * @return the <code>HashMap</code> of answers.
	 * 
	 * @gwt.typeArgs <java.lang.String, java.lang.String>
	 */
	public HashMap getHashMap() {
		return answers;
	}

	public void setMergeable(boolean mergeable) {
		this.mergeable = mergeable;
	}

	public boolean isMergeable() {
		return mergeable;
	}

	/**
	 * @param table_name
	 *            the table_name to set
	 */
	public void setTableName(String table_name) {
		this.tableName = table_name;
	}

	/**
	 * @return the table_name
	 */
	public String getTableName() {
		return tableName;
	}

	/**
	 * Returns whether this <code>Answer</code> is empty or not.
	 * 
	 * @return whether this <code>Answer</code> is empty or not.
	 */
	public boolean isEmpty() {

		boolean empty = false;

		// if we don't have a table name, we're empty
		if (Util.nullEmpty(tableName)) {
			empty = true;

			// if we don't have any other information, aside from the table
			// name, then we're still empty.
		} else if (!empty && (answers == null || answers.isEmpty())
				&& (where_clauses == null || where_clauses.isEmpty())
				&& (remove_keys == null || remove_keys.isEmpty())) {
			empty = true;
		}

		// If we have answers and nothing else, we need to insure that they're
		// not all "ignores".
		if (!empty && answers != null && !answers.isEmpty()
				&& (where_clauses == null || where_clauses.isEmpty())
				&& (remove_keys == null || remove_keys.isEmpty())) {

			// go through everything in the answers
			HashMap the_answer = getHashMap();
			Iterator keys = the_answer.keySet().iterator();

			// and check that they're not all ignore.
			boolean allIgnore = true;
			while (keys.hasNext()) {
				String key = (String) keys.next();
				if (!key.equals("ignore")
						&& ((String) the_answer.get(key)).length() > 0) {
					allIgnore = false;
				}
			}
			empty = allIgnore;
		}
		return empty;
	}
}
